package mymyoux.io.sockets.clients
{
	
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.Socket;
	import flash.utils.ByteArray;
	
	import mymyoux.debug.log;
	import mymyoux.io.sockets.ISocket;
	import mymyoux.io.sockets.mapping.Telegramme;
	import mymyoux.signals.Signal;
	
	public class BinarySocket implements ISocket
	{
		/**
		 * @private
		 */
		private var _port:uint;
		/**
		 * @private
		 */
		private var _host:String;
		/**
		 * @private
		 */
		private var _socket:Socket;
		//Signals
		/**
		 * @private
		 */
		 private var _signalData:Signal;
		/**
		 * @private
		 */
		private var _signalError:Signal;
		/**
		 * @private
		 */
		private var _signalStatus:Signal;
		/**
		 * @private
		 */
		 private var _signalPolicy:Signal;
		/**
		 * Request made by incoming socket, asking for security policy.
		 */
		protected const SECURITY_POLICY_REQUEST:String="<policy-file-request/>";
		 /**
		 * @private
		 */
		 private var has_already_sent:Boolean;
		public static const TIMEOUT:String = "timeout";
		public function BinarySocket()
		{
			_port = 0;
			_host = "";
			_socket = new Socket();
			has_already_sent = false;
			//signals
			_signalData = new Signal(BinarySocket, ByteArray);
			_signalStatus = new Signal(BinarySocket, Boolean);
			_signalError = new Signal(BinarySocket, Event);
			_signalPolicy = new Signal(BinarySocket);
		}
		/**
		 * Connexion status
		 */
		public function get connected():Boolean
		{
			return _socket.connected;
		}
		/**
		 * @inheritDoc
		 */
		public function get port():uint
		{
			return _port;
		}
		public function set port(value:uint):void
		{
			if(value>65535)
			{
				throw(new Error("Incorrect port [value="+value+"]"));
			}
			_port = value;
		}
		/**
		 * @inheritDoc
		 */
		public function get host():String
		{
			return _host;
		}
		public function set host(value:String):void
		{
			var i:int = value.lastIndexOf(":");
			if(i > -1)
			{
				try
				{
					var port:int = Number(value.substring(i+1));
					if(port > 0)
					{
						value = value.substring(0, i);
						this.port = port;
					}
				}catch(error:Error)
				{
					
				}
			}
			if(value == null || value.length < 3)
			{
				throw(new Error("Incorrect IP"));
			}
			_host = value;
		}
		/**
		 * @inheritDoc
		 */
		public function get signalError():Signal
		{
			return _signalError;
		}
		/**
		 * @inheritDoc
		 */
		public function get signalStatus():Signal
		{
			return _signalStatus;
		}
		public function get signalData():Signal
		{
			return _signalData;
		}
		/**
		 * only active if a socket from a serverSocket received a connection.
		 */
		public function get signalPolicy():Signal
		{
			return _signalPolicy;
		}
		public function get socket():Socket
		{
			return _socket;
		}
		public function set socket(value:Socket):void
		{
			if(_socket != null)
			{
				signalStatus.dispatch(this, false);
				closeSocket();
			}
			_socket = value;
			if(_socket != null)
			{
				prepareSocket();
			}
			
		}
		/**
		 * @inheritDoc
		 */
		public function connect():void
		{
			host = host; //throws error if incorrect ip
			port = port; //throws error if incorrect port
			if(_socket == null)
			{
				_socket = new Socket();
			}
			prepareSocket();
			_socket.connect(host, port);
		}
		//TODO: check this equality
		public function equals(socket:BinarySocket):Boolean
		{
			return socket.host === host && socket.port === port && socket === this;
		}
		protected function prepareSocket():void
		{
			if(_socket != null)
			{
				_socket.addEventListener(Event.CONNECT, onConnect);
				_socket.addEventListener(IOErrorEvent.IO_ERROR, onIOError);
				_socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
				_socket.addEventListener(ProgressEvent.SOCKET_DATA, onProgress);
				_socket.addEventListener(Event.CLOSE, onClose);
			}
		}
		protected function closeSocket():void
		{
			_socket.removeEventListener(Event.CONNECT, onConnect);
			_socket.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);
			_socket.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onSecurityError);
			_socket.removeEventListener(ProgressEvent.SOCKET_DATA, onProgress);
			_socket.removeEventListener(Event.CLOSE, onClose);
			_socket = null;
		}
		protected function onClose(event:Event):void
		{
			signalStatus.dispatch(this, false);
		}
		protected function onConnect(event:Event):void
		{
			signalStatus.dispatch(this, true);
		}
		protected function onIOError(event:IOErrorEvent):void
		{
			signalError.dispatch(this, event);
		}
		protected function onProgress(event:ProgressEvent):void
		{
		
			var byteArray:ByteArray;
			while(socket.bytesAvailable > 0)
			{
				try
				{
					byteArray = new ByteArray();
					socket.readBytes(byteArray, 0, socket.bytesAvailable);
					if(!has_already_sent)
					{
						has_already_sent = !has_already_sent;
						if(byteArray.toString().substring(0,SECURITY_POLICY_REQUEST.length)==SECURITY_POLICY_REQUEST)
						{
							_signalPolicy.dispatch(this);
							break;
						}
					}
					byteArray.position = 0;
					
				}catch(error:TypeError)
				{
					trace("BinarySocketError Reading: ["+error+"]");
					continue;
				}
				catch(error:Error)
				{
					trace("BinarySocketError Reading: ["+error+"]");
					break;
				}
				_signalData.dispatch(this, byteArray);
			}
			
		}
		protected function onSecurityError(event:SecurityErrorEvent):void
		{
			signalError.dispatch(this, event);
		}
		public function toString():String
		{
			return "[Socket host=\""+host+"\" port=\""+port+"\"]";
		}
		public function write(value:Object):void
		{
			trace("send:"+value);
			if(socket.connected)
			{
				socket.writeObject(value);
				socket.flush();
			}
		}
		public function close():void
		{
			try
			{
				socket.close();
				log("close");
			}catch(error:Error)
			{
				log("Error_Closing_binarysocket:["+error+"]");
			}
			signalError.removeAll();
			signalPolicy.removeAll();
			signalStatus.removeAll();
		}
		
	}
}